package com.jwater.core.common.param;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jwater.com.alibaba.fastjson.JSON;
import jwater.com.alibaba.fastjson.JSONArray;
import jwater.com.alibaba.fastjson.JSONObject;
import jwater.com.alibaba.fastjson.serializer.SerializeConfig;
import jwater.org.springframework.beans.BeanUtils;
import jwater.org.springframework.beans.BeanWrapper;
import jwater.org.springframework.beans.PropertyAccessorFactory;

/**
 * @description:
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
public class ParamMapImpl implements ParamMap {
    private static final long serialVersionUID = -8464613544119685891L;
    protected static Logger logger = LoggerFactory.getLogger(ParamMapImpl.class);

    protected Map<String, Object> params = new HashMap<String, Object>();

    /**
     * @param map
     * @return
     */
    public static ParamMap wrap(Map<String, Object> map) {
        ParamMapImpl paramMapImpl = new ParamMapImpl();
        paramMapImpl.params = map;
        return paramMapImpl;
    }

    /**
     * @param json
     * @return
     */
    public static ParamMap wrapJson(String json) {
        JSONObject jsonObj = JSON.parseObject(json);
        return toParamMap(jsonObj);
    }

    /**
     * @param jsonObj
     * @return
     */
    private static ParamMap toParamMap(JSONObject jsonObj) {
        ParamMap paramMap = new ParamMapImpl();
        for (Entry<String, Object> en : jsonObj.entrySet()) {
            if (en.getValue() instanceof JSONObject) {
                paramMap.put(en.getKey(), toParamMap((JSONObject) en.getValue()));
            } else if (en.getValue() instanceof JSONArray) {
                List<ParamMap> list = new ArrayList<ParamMap>();
                for (int i = 0; i < ((JSONArray) en.getValue()).size(); i++) {
                    list.add(toParamMap(((JSONArray) en.getValue()).getJSONObject(i)));
                }
                paramMap.put(en.getKey(), list);
            } else {
                paramMap.put(en.getKey(), en.getValue());
            }
        }
        return paramMap;
    }

    /**
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
        params.put(key, value);
    }

    /**
     * @param key
     * @return
     */
    public Object getObject(String key) {
        return params.get(key);
    }

    /**
     * @param key
     * @return
     */
    public ParamMap getParamMap(String key) {
        ParamMap param = (ParamMap) params.get(key);
        param = param == null ? new ParamMapImpl() : param;
        return param;
    }

    /**
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    public List<ParamMap> getParamMaps(String key) {
        Object obj = params.get(key);
        if (obj == null) {
            return new ArrayList<ParamMap>();
        }
        if (obj instanceof ParamMap) {
            List<ParamMap> list = new ArrayList<ParamMap>();
            list.add((ParamMap) obj);
            return list;
        }
        return (List<ParamMap>) obj;
    }

    /**
     * @return
     */
    public Set<Entry<String, Object>> entrySet() {
        return params.entrySet();
    }

    /**
     * @param key
     * @return
     */
    public boolean containsKey(String key) {
        return params.containsKey(key);
    }

    /**
     * @param key
     * @return
     */
    public String getString(String key) {
        Object value = params.get(key);
        return value == null ? null : value.toString();
    }

    /**
     * @param key
     * @param defaultValue
     * @return
     */
    public String getString(String key, String defaultValue) {
        Object value = params.get(key);
        return value == null ? defaultValue : value.toString();
    }

    /**
     * @param key
     * @return
     */
    public Long getLong(String key) {
        Object value = params.get(key);
        return value == null ? null : Long.parseLong(value.toString().trim());
    }

    /**
     * @param key
     * @param defaultValue
     * @return
     */
    public Long getLong(String key, Long defaultValue) {
        Object value = params.get(key);
        return value == null ? defaultValue : Long.parseLong(value.toString().trim());
    }

    /**
     * @param key
     * @return
     */
    public Integer getInt(String key) {
        Object value = params.get(key);
        return value == null ? null : Integer.parseInt(value.toString().trim());
    }

    /**
     * @param key
     * @param defaultValue
     * @return
     */
    public Integer getInt(String key, Integer defaultValue) {
        Object value = params.get(key);
        return value == null ? defaultValue : Integer.parseInt(value.toString().trim());
    }

    /**
     * @param key
     * @return
     */
    public Double getDouble(String key) {
        Object value = params.get(key);
        return value == null ? null : Double.parseDouble(value.toString().trim());
    }

    /**
     * @param key
     * @param defaultValue
     * @return
     */
    public Double getDouble(String key, Double defaultValue) {
        Object value = params.get(key);
        return value == null ? defaultValue : Double.parseDouble(value.toString().trim());
    }

    /**
     * @param key
     * @return
     */
    public Float getFloat(String key) {
        Object value = params.get(key);
        return value == null ? null : Float.parseFloat(value.toString().trim());
    }

    /**
     * @param key
     * @param defaultValue
     * @return
     */
    public Float getFloat(String key, Float defaultValue) {
        Object value = params.get(key);
        return value == null ? defaultValue : Float.parseFloat(value.toString().trim());
    }

    /**
     * @return
     */
    public Map<String, Object> getParams() {
        return params;
    }

    /**
     * @return
     */
    public String toJson() {
        SerializeConfig serializeConfig = new SerializeConfig();
        serializeConfig.put(ParamMapImpl.class, ParamMapCodec.instance);
        return JSON.toJSONString(params, serializeConfig);
    }

    /**
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T toObj(Class<T> clazz) {
        T mappedObject = BeanUtils.instantiate(clazz);
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(clazz);
        Map<String, PropertyDescriptor> mappedFields = new HashMap<String, PropertyDescriptor>();
        for (PropertyDescriptor pd : pds) {
            if (pd.getWriteMethod() != null) {
                mappedFields.put(pd.getName().toLowerCase(), pd);
            }
        }
        for (Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            PropertyDescriptor pd = mappedFields.get(key.toLowerCase());
            if (pd != null) {
                bw.setPropertyValue(pd.getName(), value);
            } else {
                logger.warn("Unable to map key[" + key + "] to obj property ");
            }
        }
        return mappedObject;
    }

    /**
     * @param prefix
     * @return
     */
    @Override
    public ParamMap getByPrefix(String prefix) {
        return getByPrefix(prefix, true);
    }

    /**
     * @param prefix
     * @param replace
     * @return
     */
    @Override
    public ParamMap getByPrefix(String prefix, boolean replace) {
        Map<String, Object> conf = new HashMap<String, Object>();
        for (Entry<String, Object> en : params.entrySet()) {
            String key = en.getKey();
            if (key.startsWith(prefix + ".")) {
                if (replace) {
                    key = key.replaceFirst(prefix + ".", "");
                }
                conf.put(key, en.getValue());
            }
        }
        return ParamMapImpl.wrap(conf);
    }

    /**
     * @return
     */
    @Override
    public String join() {
        return join("");
    }

    /**
     * @param splitStr
     * @return
     */
    @Override
    public String join(String splitStr) {
        StringBuilder sb = new StringBuilder();
        for (Entry<String, Object> en : params.entrySet()) {
            if (sb.length() != 0) {
                sb.append(splitStr);
            }
            sb.append(en.getValue().toString());
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        return params.toString();
    }
}
